#include "Scheduler.h"
#include <iostream>
#include <string>

using namespace std;


Scheduler::Scheduler(int sleep_time,int quantom,Shell1* shell,Mailer* mailer) 
:	m_NodesCount(3),
	total_time(0),
	m_SleepTime(sleep_time),
	m_Quantom(quantom),
	myShell(shell),
	myMailer(mailer)
{
	for (int i=0; i<=3;i++)
	{
	scp[i]=true;
	}
}

// ~Scheduler()
//		deletes the ThreadsTable
//
Scheduler::~Scheduler()
{
  delete myMailer;
  delete myShell;
}


void Scheduler::Create()
{
  resetSystemInfo(); 
  for (int i = 0; i < m_NodesCount; i++)
  {
    m_NodesTableInfo[i]=temp;
  }

}

void Scheduler::setNodesCount(int size)
{
	m_NodesCount=size;
}

void Scheduler::resetSystemInfo()
{
  temp.priority=0;
  temp.tq_createNet=0;
  temp.status=0;
 
  shellInfo=temp;
  mailerInfo=temp;
}

void Scheduler::setNodeThreads(map<int,Node*> nodes)
{
	nodeThreads=nodes;
}

// Start()
//		start scheduling.
//
//
void Scheduler::Start()
{
	int j,n,l;
	while(true){
		while (scp[0]) //simple scheduling on startup 
		{
		SimpleScheduling();
		}
		while (scp[1])  //this is Round Robin Policy
		{
		RoundRobin(j,n);
		}
		l=0;
		bool* ranThreads=new bool[m_NodesCount+2];
		for(int u=0;u<m_NodesCount+2;u++)
			ranThreads[u]=false;
		numOfRuns=0;
		while (scp[2])  //this is longest Queue Policy
		{
			LongestQueue(l,ranThreads,n);
		}
		for(int u=0;u<m_NodesCount+2;u++)
			ranThreads[u]=false;
		numOfRuns=0;
		while (scp[3]) //this is our choice of priority Policy
		{
			SpecialPolicy(ranThreads,n);
		}
	}
}

void Scheduler::ChoosePolicy(int j)
{
for (int i=0;i<=3;i++)
{
	if (i==j)
		scp[i]=true;
	else scp[i]=false;
}
}

void Scheduler::SimpleScheduling()
{
		int n=0;
		timer.reset();
		timer.start();
		n=myShell->run(m_Quantom);
		timer.stop();
		shellInfo.status=n;
		shellInfo.tq_createNet+=(double)timer;
		if (!myMailer->isBlocked())
		{
		timer.reset();
		timer.start();
		n=myMailer->run(m_Quantom);
		timer.stop();
		mailerInfo.status=n;
		mailerInfo.tq_createNet+=(double)timer;
		}
}

void Scheduler::RoundRobin(int j, int n)
{
		timer.reset();
		timer.start();
		n=myShell->run(m_Quantom);
		timer.stop();
		shellInfo.status=n;
		shellInfo.tq_createNet+=(double)timer;
		if (mailerInfo.status!=-2 && !myMailer->isBlocked())
		{
		timer.reset();
		timer.start();
		n=myMailer->run(m_Quantom);
		mailerInfo.status=n;
		timer.stop();
		}
		mailerInfo.tq_createNet+=(double)timer;
   		for(j=0;j<m_NodesCount;j++) 
			{
				if(m_NodesTableInfo[j].status!=-2 && !nodeThreads[j]->isBlocked())
				{		
				timer.reset();
				timer.start();		
				n=nodeThreads[j]->run(m_Quantom);
				m_NodesTableInfo[j].status=n;
				timer.stop();
				m_NodesTableInfo[j].tq_createNet+=(double)timer;
				}
			}
	//	printf("m_NodesTableInfo[1].status is: %d\n",m_NodesTableInfo[1].status);
}

void Scheduler::LongestQueue(int l,bool* ranThreads,int n)
{
			int* k=getLongestQueue();
			int b=0;
			if (numOfRuns==(m_NodesCount+2)*2)
				{
				checkAndSolveStarvation(ranThreads);
				numOfRuns=0;
				}
			bool ran=false;
			if(k[0]!=-6)
			{
				for(int g=0;g<m_NodesCount+2 &&(!ran);g++)
				{
					//printf("the next to run is %d or %d and g is: %d\n",k[g],l,g);
					if (k[g]==-2 && !myShell->isBlocked())
					{
					timer.reset();
					timer.start();
					n=myShell->run(m_Quantom);
					timer.stop();
					shellInfo.status=n;
					shellInfo.tq_createNet+=(double)timer;
					ranThreads[0]=true;
					if (n!=-1)
					ran=true;
					}
					else if(mailerInfo.status!=-2 && k[g]==-1 && !myMailer->isBlocked())
					{
				//	printf("indeeed\n");
					timer.reset();
					timer.start();
					n=myMailer->run(m_Quantom);
				//	printf("status is: %d\n",n);
				//	printf("mailers mailbox size is: %d\n",myMailer->getMailBoxSize());
					timer.stop();
					mailerInfo.status=n;
					mailerInfo.tq_createNet+=(double)timer;
					ranThreads[1]=true;
					if (n!=-1)
					ran=true;
					}
					else if(m_NodesTableInfo[k[g]].status!=-2 && k[g]>=0 && !nodeThreads[k[g]]->isBlocked())
					{
					timer.reset();
					timer.start();
					n=nodeThreads[k[g]]->run(m_Quantom);
					timer.stop();
					m_NodesTableInfo[k[g]].status=n;
					m_NodesTableInfo[k[g]].tq_createNet+=(double)timer;
					ranThreads[k[g]+2]=true;
					if (n!=-1)
					ran=true;
					}
				}
			}
			else
			{
				if (l==m_NodesCount)
					{
					timer.reset();
					timer.start();
					n=myShell->run(m_Quantom);
					timer.stop();
					shellInfo.status=n;
					shellInfo.tq_createNet+=(double)timer;
					ranThreads[0]=true;
					}
				else if (mailerInfo.status!=-2 && l==m_NodesCount+1 && !myMailer->isBlocked())
					{
					timer.reset();
					timer.start();
					n=myMailer->run(m_Quantom);
					timer.stop();
					mailerInfo.status=n;
					mailerInfo.tq_createNet+=(double)timer;
					ranThreads[1]=true;
					}
				else
				{
					if (m_NodesTableInfo[l].status!=-2 && !nodeThreads[l]->isBlocked())
					{
					timer.reset();
					timer.start();
					n=nodeThreads[l]->run(m_Quantom);
					timer.stop();
					m_NodesTableInfo[l].status=n;
					m_NodesTableInfo[l].tq_createNet+=(double)timer;
					ranThreads[l+2]=true;
					}
				}
				l++;
				l=l%(m_NodesCount+2);
			}
	numOfRuns++;
}

void Scheduler::SpecialPolicy(bool* ranThreads,int n)
{
	int* k=getLongestQueue();
	int b=0;
	bool ran=false;
	if (numOfRuns==(m_NodesCount+2)*2)
	{
		checkAndSolveStarvation(ranThreads);
		numOfRuns=0;
	}
	if (mailerInfo.status!=-2 && !myMailer->isBlocked())
	{
	timer.reset();
	timer.start();
	n=myMailer->run(m_Quantom*2);
	timer.stop();
	mailerInfo.status=n;
	mailerInfo.tq_createNet+=(double)timer;
	ranThreads[1]=true;
	}
	timer.reset();
	timer.start();
	n=myShell->run(m_Quantom);
	timer.stop();
	shellInfo.status=n;
	shellInfo.tq_createNet+=(double)timer;
	ranThreads[0]=true;
	for(int g=0;g<m_NodesCount+2 &&(!ran);g++)
		{

			if(m_NodesTableInfo[k[g]].status!=-2 && k[g]>=0 && !nodeThreads[k[g]]->isBlocked())
			{
			timer.reset();
			timer.start();
			n=nodeThreads[k[g]]->run(m_Quantom);
			timer.stop();
			m_NodesTableInfo[k[g]].status=n;
			m_NodesTableInfo[k[g]].tq_createNet+=(double)timer;
			ranThreads[k[g]+2]=true;
			if (n!=-1)
			ran=true;
			}
		}
}

double Scheduler::getTimeNodeI(int NodeID)
{
if (NodeID==-2)//-2 is the ID for the shell
  return shellInfo.tq_createNet;
else if (NodeID==-1)//-1 is the ID for the mailer
  return mailerInfo.tq_createNet;
else if (!nodeThreads.empty())
  {
  return m_NodesTableInfo[NodeID].tq_createNet;
  }
return -3;
}
	
double Scheduler::getTime()
{
	total_time=shellInfo.tq_createNet+mailerInfo.tq_createNet;
	if (!nodeThreads.empty())
	{
	for (int i=0; i<=m_NodesCount;i++)
		total_time+=m_NodesTableInfo[i].tq_createNet;
	}
	return total_time;
}
	
int Scheduler::getMaxLengthI(int NodeID)
{
if(NodeID==-2)
	return myShell->getMailBoxMaxSize();
if (!nodeThreads.empty())
	return nodeThreads[NodeID]->getMailBoxMaxSize();
return -3;
}

int* Scheduler::getLongestQueue()
{
	bool tie=true;
	int temp1=0;
	int temp2=0;
	int* currents=new int[m_NodesCount+2];
	int* PQ=new int[m_NodesCount+2];
	int current=-2;
	currents[0]=current;
	int max=myShell->getMailBoxSize();
	PQ[0]=max;
	if (max < myMailer->getMailBoxSize())
		{
		max=myMailer->getMailBoxSize();
		current=-1;
		PQ[1]=PQ[0];
		PQ[0]=max;
		currents[1]=currents[0];
		currents[0]=current;
		tie=false;
		}
	for(int h=2;h<m_NodesCount+2;h++)
		{
		PQ[h]=nodeThreads[h-2]->getMailBoxSize();
		//printf("P[h] is %d\n",PQ[h]);
		currents[h]=h-2;
		if (max < PQ[h])
			tie=false;
		}
	if (!tie)
	{
	for (int c=0; c<m_NodesCount+2;c++)
		for (int y=0; y<m_NodesCount+2;y++)
			if(PQ[y] < PQ[c])
				{
				temp1=PQ[c];
				PQ[c]=PQ[y];
				PQ[y]=temp1;
				temp2=currents[c];
				currents[c]=currents[y];
				currents[y]=temp2;
				}
	}	
	if (tie)
		currents[0]=-6;
	return currents;
}

int Scheduler::getStatus(int NodeID)
{
	if(NodeID==-2)
		return shellInfo.status;
	else if (NodeID==-1)
		return mailerInfo.status;
	return m_NodesTableInfo[NodeID].status;
}

void Scheduler::CrashNode(int id) {
	//cout<<"Thread id"<<id<<"is going to get Crushed>"<<endl;
	//nodeThreads[id]->block();
	m_NodesTableInfo[id].status=-2;
	//printf("it has been crushed\n");
}

void Scheduler::ReviveNode(int id)
{
	//cout<<"Thread id"<<id<<"is about to get Revived"<<endl;
	//nodeThreads[id]->block();
	m_NodesTableInfo[id].status=0;
	//printf("Threas %d has been revived\n",id);
}

bool Scheduler::AllZombies()
{
bool zombies=true;
for(int k=0; k<m_NodesCount && zombies;k++)
	if (m_NodesTableInfo[k].status!=-2)
		zombies=false;
return zombies;
}

void Scheduler::KillAll()
{
	SystemCalls* system=SystemCalls::getInstance();
	while (!AllZombies())
	{
		for (int j=0; j<m_NodesCount;j++)
			if(!system->Deadlock(j))
				m_NodesTableInfo[j].status=-2;
	}
  	while( !nodeThreads.empty() ) 
	{
    		//cout << "Killing: " << (*nodeThreads.begin()).first << endl;
   		 nodeThreads.erase( nodeThreads.begin() );
 	}
	//myMailer->emptyMailBox();
	while(mailerInfo.status!=-2)
	{
		mailerInfo.status=-2;
	}
	myMailer->exit();
	delete myMailer;
	myMailer=new Mailer();
	myShell->cleanMailbox();
	resetSystemInfo();
	total_time=0;
	numOfRuns=0;
	//delete system;
	//printf("killed\n");
}

int Scheduler::checkAndSolveStarvation(bool* ranThreads)
{
	int n=0;
	for (int k=0;k<m_NodesCount+2;k++)
		if (!ranThreads[k])
			{
			//printf("thread %d was starved\n",k-2);
			if (k==0)
				{
				timer.reset();
				timer.start();
				n=myShell->run(m_Quantom);
				timer.stop();
				shellInfo.tq_createNet+=(double)timer;
				shellInfo.status=n;
				ranThreads[0]=true;
				}
			else if (k==1 && mailerInfo.status!=-2 && !myMailer->isBlocked())
				{
				timer.reset();
				timer.start();
				n=myMailer->run(m_Quantom);
				timer.stop();
				mailerInfo.status=n;
				mailerInfo.tq_createNet+=(double)timer;
				ranThreads[1]=true;
				}
			else 
				{
				if(m_NodesTableInfo[k-2].status!=-1 &&
m_NodesTableInfo[k-2].status!=-2 && !nodeThreads[k-2]->isBlocked())
				{
				timer.reset();
				timer.start();
				n=nodeThreads[k-2]->run(m_Quantom);
				timer.stop();
				m_NodesTableInfo[k-2].status=n;
				m_NodesTableInfo[k-2].tq_createNet+=(double)timer;
				ranThreads[k-2]=true;
				}
				}
			}
	for(int u=0;u<m_NodesCount+2;u++)
		ranThreads[u]=false;
}

bool Scheduler::isNodeInserting(int id)
{
	return nodeThreads[id]->getIsInserting();
};







